/*
 *  Copyright (C) 2010 Martin Haulrich <mwh.isv@cbs.dk> and Matthias Buch-Kromann <mbk.isv@cbs.dk>
 *
 *  This file is part of the IncrementalParser package.
 *
 *  The IncrementalParser program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.osdtsystem.incparser.features;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.osdtsystem.incparser.featureextraction.intlist.IntList;

/**
 *
 * @author Martin Haulrich and Matthias Buch-Kromann
 */
public class FeatureHandlerMapBased implements FeatureHandler {
    HashMap<Object,Integer> map = new HashMap<Object,Integer>();
    //TObjectIntHashMap<Object> map = new TObjectIntHashMap<Object>();
    HashMap<Integer,String> names = new HashMap<Integer,String>();
    int nextFeatureID;
    boolean insertUnseenFeatures = true;
    boolean hasStandardFeatures;

    public FeatureHandlerMapBased(boolean addStandardFeatures) {
        this.hasStandardFeatures = addStandardFeatures;
        nextFeatureID = addStandardFeatures ? FeatureHandler.STANDARDFEATURES : 0;
    }

    public final int features() {
        return map.size();
    }

    public final int insertFeature(Object key) {
        return map.containsKey(key)
                ? map.get(key)
                : _insertFeature(key);
    }

    public final int insertFeature(Object key, String name) {
        int feature = insertFeature(key);
        if (feature != NA)
            names.put(feature, name);
        return feature;
    }

    final int _insertFeature(Object key) {
        if (! insertUnseenFeatures)
            return NA;
        if (key instanceof IntList) {
            map.put(((IntList) key).clone(), nextFeatureID);
        } else if (key instanceof String) {
            map.put(new String((String) key), nextFeatureID);
        } else {
            map.put(key, nextFeatureID);
        }
        return nextFeatureID++;
    }

    public String featureName(int feature) {
        if (names.containsKey(feature))
            return names.get(feature);
        return "@[" + feature + "]";
    }

    public String featureName(Object key) {
        return featureName(feature(key));
    }

    public int feature(Object name) {
        if (map.containsKey(name))
            return map.get(name);
        return _insertFeature(name);
    }

    public void stopGrowth() {
        insertUnseenFeatures = false;
    }

    public void setFeatureGrowth(boolean insertUnseenFeatures) {
        this.insertUnseenFeatures = !insertUnseenFeatures;
    }

    /*public int reserveFeatures(int count) {
    int firstReservedFeature = nextFeatureID;
    nextFeatureID += count;
    return firstReservedFeature;
     }*/

    public boolean hasStandardFeatures() {
        return hasStandardFeatures;
    }

    public void writeToFile(String outputFile) {
        Writer writer = null;
        try {
            writer = new PrintWriter(outputFile, "UTF-8");
            writeToFile(writer);
            writer.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(FeatureHandlerMapBased.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(FeatureHandlerMapBased.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(FeatureHandlerMapBased.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    void writeToFile(Writer writer) throws IOException {
        // Print general statistics
        writer.write("FeatureHandlerMapBased: features=" + map.size());
        writer.write(" names=" + names.size() + "\n");

        // Print all features
        for (ObjectIntEntry entry : sortedEntries()) {
            writer.write("" + entry.value);
            writer.write("=");
            writer.write(keyAsString(entry.key));
            if (names.containsKey(entry.value)) {
                writer.write(" [" + names.get(entry.value) + "]");
            }
            writer.write("\n");
        }
    }

    String keyAsString(Object object) {
        if (object instanceof byte[]) {
            return bytesAsString((byte[]) object);
        } else if (object instanceof int[]) {
            int[] checked = (int[]) object;
            StringBuilder s = new StringBuilder();
            s.append("[").append(checked[0]);
            for (int i = 1; i < checked.length; ++i)
                s.append(",").append(checked[i]);
            s.append("]");
            return s.toString();
        }
        return object.toString();
    }

    String bytesAsString(byte[] object) {
        try {
            return new String(object, "UTF-8");
        } catch (UnsupportedEncodingException ex) {
            return "_BADENCODING_";
        }
    }

    ObjectIntEntry[] sortedEntries() {
        // Store map as array of object-int pairs
        ObjectIntEntry[] entries = new ObjectIntEntry[map.size()];
        //TObjectIntIterator iterator = map.iterator();
        //for(int i = 0; iterator.hasNext(); ++i) {
//            iterator.advance();
//            entries[i] = new ObjectIntEntry(iterator.key(),
//                    iterator.value());
//        }
        int i = 0;
        for (Entry<Object,Integer> entry : map.entrySet()) {
            entries[i++] = new ObjectIntEntry(entry.getKey(),
                    entry.getValue());
        }

        // Sort array
        Arrays.sort(entries);
        return entries;
    }

    // Inner class for representing object-int pairs
    class ObjectIntEntry implements Comparable<ObjectIntEntry> {
        Object key;
        int value;
        ObjectIntEntry(Object key, int value) {
            this.key = key;
            this.value = value;
        }
        public int compareTo(ObjectIntEntry o) {
            if (this.value < o.value)
                return -1;
            if (this.value > o.value)
                return 1;
            return 0;
        }
    }
}

